package sc.common.test;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

/**
 * 任务计时器
 * 用于记录互斥任务（步骤）各自运行时间
 * 多线程不安全
 */
public class JobStopWatch {

    /**
     * "HH:mm:ss.SSS"
     */
    public JobStopWatch(String watchName, String dateFormat) {
        this.watchName = watchName;
        this.dateFormat = new SimpleDateFormat(dateFormat);
        this.jobs = new ArrayList<Object>();
    }

    String            watchName;
    SimpleDateFormat  dateFormat;
    ArrayList<Object> jobs;

    public String format(Date d) {
        return d == null ? null : dateFormat.format(d);
    }

    public static class Job {
        public Job(String jobName) {
            this.jobName = jobName;
            this.isRunning = false;
            this.runMillis = 0;
        }

        public void begin() {
            if (isRunning) {
                return;
            } else {
                if (beginTime == null) {
                    beginTime = new Date();
                } else {
                    endTime = new Date();
                }
                isRunning = true;
            }
        }

        public void end() {
            if (!isRunning) {
                return;
            } else {
                Date now = new Date();
                if (endTime == null) {
                    runMillis += (now.getTime() - beginTime.getTime());
                } else {
                    runMillis += (now.getTime() - endTime.getTime());
                }
                endTime = now;
                isRunning = false;
            }
        }

        public long getRunMillis() {
            return runMillis;
        }

        public String  jobName;
        public Date    beginTime;
        public Date    endTime;
        public long    runMillis;
        public boolean isRunning;
    }

    Job curJob;

    /**
     * 切换步骤开始
     * @param job 步骤唯一标识
     */
    public void begin(String jobName) {
        end();
        Job newJob = new Job(jobName);
        jobs.add(newJob);
        newJob.begin();
        curJob = newJob;
    }

    public JobStopWatch beginSubWatch(String watchName, String dateFormat) {
        end();
        JobStopWatch newWatch = new JobStopWatch(watchName, dateFormat);
        jobs.add(newWatch);
        return newWatch;
    }

    /**
     * 当前步骤结束
     */
    public void end() {
        if (curJob != null) {
            curJob.end();
            curJob = null;
            return;
        }
    }

    @Override
    public String toString() {
        long startTime = new Date().getTime();
        StringBuffer sb = new StringBuffer();
        sb.append("JobStopWatch");
        sb.append("<").append(watchName).append(">\r\n");
        toString(this, sb, startTime, 0, false);
        return sb.toString();
    }

    public static char   PAD          = ' ';
    public static int    PAD_NUM      = 4;
    public static int    NAME_LENGTH  = 40;
    public static String JOB_TEMPLATE = "name[%%-%ds], run   [%%10dms], begin[%%12s], end[%%12s]";
    public static String SUB_TEMPLATE = "name[%%-%ds],    sub[%%10dms], begin[%%12s], end[%%12s]";

    public static class WatchStat {
        long sumMillis;
        int  count;
        Date begin, end;
    }

    public WatchStat toString(JobStopWatch thisWatch, StringBuffer sb, long startTime, int padLeft,
                              boolean isSub) {
        WatchStat stat = new WatchStat();
        if (thisWatch == null || thisWatch.jobs == null || thisWatch.jobs.size() == 0) {
            return stat;
        }
        String pad = "";
        int padCount = padLeft;
        while (padCount > 0) {
            pad += PAD;
            padCount--;
        }
        String template = isSub ? SUB_TEMPLATE : JOB_TEMPLATE;
        template = String.format(template, NAME_LENGTH - padLeft);
        for (int i = 0; i != thisWatch.jobs.size(); ++i) {
            Object jobObj = thisWatch.jobs.get(i);
            if (jobObj instanceof Job) {
                Job job = (Job) jobObj;
                if (i == 0) {
                    stat.begin = job.beginTime;
                }
                stat.end = job.endTime;
                long jobMillis = job.getRunMillis();
                sb.append(pad)
                    .append(
                        String.format(template, job.jobName, jobMillis, format(job.beginTime),
                            format(job.endTime))).append("\r\n");
                stat.sumMillis += jobMillis;
                stat.count++;
            } else if (jobObj instanceof JobStopWatch) {
                JobStopWatch watch = (JobStopWatch) jobObj;
                WatchStat watchStat = toString(watch, sb, startTime, padLeft + PAD_NUM, true);
                sb.append(pad)
                    .append(
                        String.format(template, watch.watchName, watchStat.sumMillis,
                            format(watchStat.begin), format(watchStat.end))).append("\r\n");
                stat.sumMillis += watchStat.sumMillis;
                stat.count++;
            }
        }
        sb.append(pad).append("statistic: ").append("count = ").append(stat.count);
        sb.append(", sumMillis = ").append(stat.sumMillis);
        sb.append(", toStringMillis = ").append(new Date().getTime() - startTime);
        sb.append("\r\n");
        return stat;
    }

    public static void main(String[] args) throws InterruptedException {
        JobStopWatch w = new JobStopWatch("Test Watch", "mm:ss.SSS");
        w.begin("Job A");
        Thread.sleep(100);

        JobStopWatch b = w.beginSubWatch("Job B", "mm:ss.SSS");
        JobStopWatch c = w.beginSubWatch("Job C", "mm:ss.SSS");
        b.begin("B1");
        Thread.sleep(100);
        c.begin("C1");
        Thread.sleep(100);
        b.begin("B2");
        c.begin("C2");
        Thread.sleep(250);
        JobStopWatch d = w.beginSubWatch("Job D", "mm:ss.SSS");
        w.begin("Job E");
        Thread.sleep(400);
        System.out.print(w);
        w.end();
        System.out.print(w);
        //
        //        w.begin("Job B");
        //        Thread.sleep(200);
        //        w.begin("Job C");
        //        Thread.sleep(300);
        //        System.out.print(w);
        //        w.end();
        //        System.out.print(w);
        //        w.begin("Job D");
        //        Thread.sleep(400);
        //        System.out.print(w);
    }
}
